﻿Imports Mystery.DbConnection
Imports System.Data.SqlClient

<GlobalAvalibleObjectImplementation(singleton:=True)>
Public Class ContentsDatabase
    Implements IContentContainer

    Private _savers As New Dictionary(Of Type, IContentSaver)

    Private generic_content_saver_type As Type = GetType(DefaultContentSaver(Of ))

    Private Sub loadSaver(content_type As Type)
        Dim attr As ContentSaverAttribute = getMystery().AssemblyRegister.getMysteryClassAttribute(Of ContentSaverAttribute)(content_type)
        Dim saver As IContentSaver = Nothing
        Try
            If attr.implementing_type IsNot Nothing Then
                saver = getGlobalObject(Of FastActivator).createInstance(attr.implementing_type)
            Else
                Dim this_type As Type = generic_content_saver_type.MakeGenericType(content_type)
                saver = getGlobalObject(Of FastActivator).createInstance(this_type)
            End If
        Catch ex As Exception
            MsgBox(content_type.FullName & vbNewLine & ex.Message & vbNewLine & ex.StackTrace)
            Throw
        End Try

        _savers(content_type) = saver
    End Sub

    Public Sub addContents(contents As IEnumerable(Of IContent)) Implements IContentContainer.addContents
        For Each c As IContent In contents
            Me.Add(c)
        Next
    End Sub

    Public Event contentAdded(content As IContent) Implements IContentContainer.contentAdded

    Public Event contentChanged() Implements IContentContainer.contentChanged

    Public Event contentRemoved(content As IContent) Implements IContentContainer.contentRemoved

    Private Function getAllContentTypesFrom(Of T)() As IEnumerable(Of Type)
        Dim content_type As ContentType = getMystery().AssemblyRegister.getMysteryClassAttribute(Of ContentType, T)()
        If content_type Is Nothing Then Throw New Exception(GetType(T).FullName & " is not a content type")
        Dim all_types As New HashSet(Of Type) From {GetType(T)}
        For Each child_type As Type In getMystery().AssemblyRegister.getChildTypes(Of T)()
            Dim child_content_type As ContentType = child_type.getMysteryAttribute(Of ContentType)()
            If child_content_type Is Nothing Then Continue For
            all_types.Add(child_type)
        Next
        Return all_types
    End Function

    Public Function getAll(Of T As IContent)() As IEnumerable(Of T) Implements IContentContainer.getAll
        Dim content_type As ContentType = getMystery().AssemblyRegister.getMysteryClassAttribute(Of ContentType, T)()
        If content_type Is Nothing Then Throw New Exception(GetType(T).FullName & " is not a content type")
        Dim all_types As IEnumerable(Of Type) = Me.getAllContentTypesFrom(Of T)()

        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim result_lists As New List(Of List(Of T))(all_types.Count)

        Dim cd As IContentDispatcher = getGlobalObject(Of IContentDispatcher)() 'not feacthing itself, allow cache to be used ;)

        For Each child_type As Type In all_types
            content_type = child_type.getMysteryAttribute(Of ContentType)()
            Dim content_type_name_p As New SqlParameter("name", content_type.name)
            Dim dt As DataTable = conn.executeQuery("select guid from contents where content_type=@name", content_type_name_p)
            Dim guids As New HashSet(Of Guid)
            For Each dr As DataRow In dt.Rows
                guids.Add(Guid.Parse(dr("guid")))
            Next

            Dim one_result As New List(Of T)(guids.Count)

            If Not _savers.ContainsKey(child_type) Then loadSaver(child_type)


            For Each Guid As Guid In guids
                Dim content As T = cd.getContent(Of T)(Guid)
                If content IsNot Nothing Then
                    one_result.Add(content)
                Else
                    'since feacthing from cd again it maye due to "security"
                    'conn.executeNonQuery("delete from contents where guid=@guid", New SqlParameter("guid", Guid.ToString))
                End If
            Next
            result_lists.Add(one_result)
        Next

        Dim result As New List(Of T)((From x As List(Of T) In result_lists Select x.Count).Sum)
        For Each one_result As List(Of T) In result_lists
            result.addRange(one_result)
        Next

        Return result
    End Function

    Public Function getAllTheContents() As System.Collections.Generic.IEnumerable(Of IContent) Implements IContentContainer.getAllTheContents
        Throw New CrazyImplementation("come on you should really not take in account all the contents of the database")
    End Function


    Private _content_type_map As New Dictionary(Of String, Type)

    Private Const select_from_contents As String = "select * from contents where guid=@guid"

    Public Function getContent(Of T As IContent)(guid As System.Guid) As T Implements IContentContainer.getContent
        If _content_type_map.Count = 0 Then
            'loading
            For Each Type As Type In getMystery.AssemblyRegister.getTypesMarkedWith(Of ContentType)()
                Dim attr As ContentType = getMystery.AssemblyRegister.getMysteryClassAttribute(Of ContentType)(Type)
                _content_type_map(attr.name) = Type
            Next
        End If

        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim guidp As New SqlParameter("guid", guid.ToString)
        Dim dt As DataTable = conn.executeQuery(select_from_contents, guidp)
        If dt.Rows.Count = 0 Then
            Return Nothing
        End If

        Dim content_type_name As String = dt.Rows(0)("content_type")

        If Not _content_type_map.ContainsKey(content_type_name) Then Return Nothing

        Dim content_type As Type = _content_type_map(content_type_name)
        If Not _savers.ContainsKey(content_type) Then loadSaver(content_type)
        Dim saver As IContentSaver = _savers(content_type)
        Return saver.read(guid)

    End Function

    Public Sub Add(item As IContent) Implements System.Collections.Generic.ICollection(Of IContent).Add
        If item Is Nothing Then Exit Sub
        Dim content_type As Type = item.GetType()
        If Not _savers.ContainsKey(content_type) Then loadSaver(content_type)
        Dim saver As IContentSaver = _savers(content_type)

        item.onContentSave()

        Dim result As CRUD? = saver.save(item)


        'let's inform around
        Select Case result
            Case CRUD.creation
                RaiseEvent contentAdded(item)
                RaiseEvent contentChanged()

        End Select
    End Sub

    Public Sub Clear() Implements System.Collections.Generic.ICollection(Of IContent).Clear
        Throw New CrazyImplementation("you can not wipe your database from the api")
    End Sub

    Public Function Contains(item As IContent) As Boolean Implements System.Collections.Generic.ICollection(Of IContent).Contains
        If item Is Nothing Then Return False
        Dim guid As New SqlClient.SqlParameter("guid", item.guid.ToString)
        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Return conn.executeScalar("select count(1) from contents where guid=@guid", guid)
    End Function

    Public Sub CopyTo(array() As IContent, arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of IContent).CopyTo
        Throw New CrazyImplementation("come on you should really not take in account all the contents of the database")
    End Sub

    Public ReadOnly Property Count As Integer Implements System.Collections.Generic.ICollection(Of IContent).Count
        Get
            Dim conn As IConnection = getGlobalObject(Of IConnection)()
            Return conn.executeScalar("select count(1) from contents")
        End Get
    End Property

    Public ReadOnly Property IsReadOnly As Boolean Implements System.Collections.Generic.ICollection(Of IContent).IsReadOnly
        Get
            Return False
        End Get
    End Property

    Public Function Remove(item As IContent) As Boolean Implements System.Collections.Generic.ICollection(Of IContent).Remove
        If item Is Nothing Then Return Nothing
        Dim content_type As Type = item.GetType()
        If Not _savers.ContainsKey(content_type) Then loadSaver(content_type)
        Dim saver As IContentSaver = _savers(content_type)
        Dim result As CRUD? = saver.remove(item)
        If result = CRUD.delete Then
            RaiseEvent contentRemoved(item)
            RaiseEvent contentChanged()
            Return True
        Else
            Return False
        End If
    End Function

    Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of IContent) Implements System.Collections.Generic.IEnumerable(Of IContent).GetEnumerator
        Throw New CrazyImplementation("come on you should really not take in account all the contents of the database")
    End Function

    Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
        Throw New CrazyImplementation("come on you should really not take in account all the contents of the database")
    End Function

    Private Function getAll1(Of T As IContent)(filter As IContentFilter) As System.Collections.Generic.IEnumerable(Of T)
        If filter Is Nothing Then Return Me.getAll(Of T)()
        'Return New HashSet(Of T)((From x As T In Me.getAll(Of T)() Select x Where filter.doesMatch(x)))

        Dim content_type As ContentType = getMystery().AssemblyRegister.getMysteryClassAttribute(Of ContentType, T)()
        If content_type Is Nothing Then Throw New Exception(GetType(T).FullName & " is not a content type")
        Dim all_types As IEnumerable(Of Type) = Me.getAllContentTypesFrom(Of T)()

        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim result_lists As New List(Of List(Of T))(all_types.Count)
        For Each child_type As Type In all_types
            If Not _savers.ContainsKey(child_type) Then loadSaver(child_type)
            Dim saver As IContentSaver = _savers(child_type)
            Dim one_result As New List(Of T)(From x As T In saver.getAll(filter) Select x)
            result_lists.Add(one_result)
        Next

        Dim result As New List(Of T)((From x As List(Of T) In result_lists Select x.Count).Sum)
        For Each one_result As List(Of T) In result_lists
            result.addRange(one_result)
        Next

        Return result

    End Function

    Public Function getAll2(Of T As IContent)(filter As T) As System.Collections.Generic.IEnumerable(Of T) Implements IContentContainer.getAllByFilter
        If filter Is Nothing Then Return Me.getAll(Of T)()
        Dim ct As New ContentFilter(Of T) With {.filter = filter}
        Return Me.getAll1(Of T)(ct)
    End Function

    Public Function containsType(Of T As IContent)() As Boolean Implements IContentContainer.containsType
        Dim content_type As ContentType = getMystery().AssemblyRegister.getMysteryClassAttribute(Of ContentType, T)()
        If content_type Is Nothing Then Return False
        Dim all_types As IEnumerable(Of Type) = Me.getAllContentTypesFrom(Of T)()
        For Each type As Type In all_types
            If Not _savers.ContainsKey(type) Then loadSaver(type)
            Dim saver As IContentSaver = _savers(type)
            If saver.haveOne() Then Return True
        Next
        Return False
    End Function

    Private Shared reference_name As String = GetType(ReferenceText).Name

    Public Function getLightContentRereferece(Of T As IContent)() As IEnumerable(Of LightContentReferece) Implements IContentContainer.getLightContentRereferece
        Dim content_type As ContentType = GetType(T).getMysteryAttribute(Of ContentType)()
        Dim query As String = "select guid,[" & reference_name & "] from Contents,[" & content_type.name & "] where Contents.id=[" & content_type.name & "].id and content_type=@content_type"
        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim dt As DataTable = conn.executeQuery(query, New SqlParameter("content_type", content_type.name))
        Dim result As New List(Of LightContentReferece)(dt.Rows.Count)
        For Each dr As DataRow In dt.Rows
            result.Add(New LightContentReferece With {.guid = dr("guid"), .ref_text = dr(reference_name)})
        Next
        Return result
    End Function

    Private Const max_search_result As Integer = 20

    Public Function search(search_text As String) As System.Collections.Generic.IEnumerable(Of IContent) Implements IContentContainer.search
        Dim result As New List(Of IContent)(max_search_result)
        'let use cache if we have it
        Dim cd As IContentDispatcher = getGlobalObject(Of IContentDispatcher)()
        Dim unique As New HashSet(Of IContent)
        'first exatch matches
        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim query As String = "select guid from Contents,ContentSearch where Contents.id=ContentSearch.id and Search=@search"
        Dim dt As DataTable = conn.executeQuery(query, New SqlParameter("search", search_text))
        
        For Each dr As DataRow In dt.Rows
            Dim c As IContent = cd.getContent(Of IContent)(Guid.Parse(dr("guid")))
            If c Is Nothing OrElse unique.Contains(c) Then Continue For
            result.Add(c)
            unique.Add(c)
        Next
        If result.Count > max_search_result Then Return result

        'starting with?
        query = "select guid from Contents,ContentSearch where Contents.id=ContentSearch.id and Search like @search"
        dt = conn.executeQuery(query, New SqlParameter("search", search_text & "%"))
        For Each dr As DataRow In dt.Rows
            Dim c As IContent = cd.getContent(Of IContent)(Guid.Parse(dr("guid")))
            If c Is Nothing OrElse unique.Contains(c) Then Continue For
            result.Add(c)
            unique.Add(c)
        Next
        If result.Count > max_search_result Then Return result

        'contains?
        query = "select guid from Contents,ContentSearch where Contents.id=ContentSearch.id and Search like @search"
        dt = conn.executeQuery(query, New SqlParameter("search", "%" & search_text & "%"))
        For Each dr As DataRow In dt.Rows
            Dim c As IContent = cd.getContent(Of IContent)(Guid.Parse(dr("guid")))
            If c Is Nothing OrElse unique.Contains(c) Then Continue For
            result.Add(c)
            unique.Add(c)
        Next

        Return result
    End Function

    Public Sub New()
        'let check it
        Dim conn As IConnection = getGlobalObject(Of IConnection)()
        Dim result As DataTable = conn.executeQuery("select * from sys.tables where name=@contents", New SqlParameter("contents", "contents"))
        If result.Rows.Count = 0 Then
            conn.executeNonQuery(My.Resources.createContentsTable)
        End If

        result = conn.executeQuery("select * from sys.tables where name=@contents", New SqlParameter("contents", "ContentSearch"))
        If result.Rows.Count = 0 Then
            conn.executeNonQuery(My.Resources.createSearchTable)
        End If




    End Sub
End Class
